package com.showsu.editor.utils.youdao;

import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.google.common.base.Preconditions;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.HttpEntity;
import org.apache.http.NameValuePair;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.mime.HttpMultipartMode;
import org.apache.http.entity.mime.MultipartEntityBuilder;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.util.EntityUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.URL;
import java.net.URLConnection;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.*;

//

/**
 * @Author:  Johnplue
 * @Description: 有道语音转换asr工具类
 * @Date: Created in 12:35 下午 2023/8/15
 * @Modified By:
 */
@Slf4j
@Component
public class AudioUtil {



    //长语音识别
    @Value("${youdao.asr.long.hostUrl}")
    private String longHostUrl;

    @Value("${youdao.asr.long.appKey}")
    private String longAppKey;

    @Value("${youdao.asr.long.appSecret}")
    private String longAppSecret;

    @Value("${youdao.asr.long.langType}")
    private String longLangType;

    //长语音识别
    private static int SLICE_SICE = 10485760; // 10M
//    private static String hostUrl = "https://openapi.youdao.com";
//    static String appKey = "310ae500bc02cd1e";
//    static String appSecret = "FXWwzBQLiDhH9xs3KTEmEflieyU8Btl5";
//    static String langType = "zh-CHS";


    //短语音识别
    @Value("${youdao.asr.hostUrl}")
    private String hostUrl;

    @Value("${youdao.asr.appKey}")
    private String appKey;

    @Value("${youdao.asr.appSecret}")
    private String appSecret;

    @Value("${youdao.asr.langType}")
    private String langType;

    //短语音识别

//    private static final String YOUDAO_URL = "https://openapi.youdao.com/asrapi";
//    private static final String APP_KEY = "310ae500bc02cd1e";
//    private static final String APP_SECRET = "FXWwzBQLiDhH9xs3KTEmEflieyU8Btl5";

    public static void main(String[] args) throws Exception {
        String url = "https://showsu.oss-cn-hangzhou.aliyuncs.com/water/20230809/123123123test.mp3";
        String videoFileKey=url.substring(url.indexOf(".com/")+5);
        System.out.println(videoFileKey);
//        AudioUtil audioUtil=new AudioUtil();
//
//        Long afr=System.currentTimeMillis();
//        System.out.println("\r\n\r\n短语音转换开始时间: " + new Date(afr));
//        //短语音识别
//        String result=audioUtil.asr60(url);
//        System.out.println("\r\n\r\n短语音转写结果: " + result);
//        Long be=System.currentTimeMillis();
//        System.out.println("短语音转换结束时间：" + (be - afr));
//
//
//
//
//        afr=System.currentTimeMillis();
//        System.out.println("\r\n\r\n长语音转换开始时间: " + new Date(afr));
//        //长语音识别
//        result=audioUtil.asrLong(url);
//        System.out.println("\r\n\r\n长语音转写结果: " + result);
//        be=System.currentTimeMillis();
//        System.out.println("长语音转换结束时间：" + (be - afr));

    }

    //短语音识别
    public String asr60(String url) throws Exception {
        String q = loadAsBase64(url);
        String result = asr60(url,q);
        return result;

    }

    //短语音识别
    public String asr60(String url,String q) throws Exception {
        Map<String, String> params = new HashMap<String, String>();
        params.put("appKey", appKey);//appKey
        params.put("q", q);//要翻译的音频文件的Base64编码字符串
        params.put("langType", langType);//源语言
        params.put("format", "mp3");//语音文件的格式，mp3
        params.put("rate", "16000");//采样率， 推荐 16000 采用率
        params.put("channel", "1");//声道数， 仅支持单声道，请填写固定值1
        params.put("type", "1");//上传类型， 仅支持base64上传，请填写固定值1
        params.put("docType", "json");
        String salt = UUID.randomUUID().toString();
        params.put("salt", salt);//uuid，唯一通用识别码
        String curtime = String.valueOf(System.currentTimeMillis() / 1000);
        params.put("curtime", curtime);//秒数
        String signStr = appKey + truncate(q) + salt + curtime + appSecret;
        String sign = getDigest(signStr);
        params.put("sign", sign);//签名，通过sha256(应用ID+q+salt+curtime+密钥)生成
        params.put("signType", "v2");//签名版本

        String result = doRequest(hostUrl, params);
        log.info("asr60 短语音识别成功");
        return result;

    }


    public static String truncate(String q) {
        if (q == null) {
            return null;
        }
        int len = q.length();
        return len <= 20 ? q : (q.substring(0, 10) + len + q.substring(len - 10, len));
    }

    public static String loadAsBase64(String url) throws Exception {
        URL urlfile=new URL(url);
        URLConnection uc=urlfile.openConnection();
        byte[] data = null;
        int i=3;
        while (i-->0){
            InputStream fis = null;
            try {
                fis = uc.getInputStream();
                i=0;
                int fileSize = uc.getContentLength();
                int offset = 0;
                data = new byte[fileSize];
                while (offset<fileSize){
                    int result = fis.read(data, offset, data.length - offset);
                    if (result == -1) {
                        break;
                    }
                    offset += result;
                }
            } catch (IOException e) {
                Thread.sleep(3000);
                log.error(e.getMessage());
            } finally {
                if (fis != null) {
                    fis.close();
                }
            }
        }
        return Base64.getEncoder().encodeToString(data);
    }

    public static String doRequest(String url, Map<String,String> requestParams) throws Exception{
        String result = null;
        CloseableHttpClient httpClient = HttpClients.createDefault();
        HttpPost httpPost = new HttpPost(url);
        List<NameValuePair> params = new ArrayList<NameValuePair>();
        for (String key : requestParams.keySet()) {
            params.add(new BasicNameValuePair(key, requestParams.get(key)));
        }
        httpPost.setEntity(new UrlEncodedFormEntity(params, "UTF-8"));
        CloseableHttpResponse httpResponse = httpClient.execute(httpPost);
        try {
            HttpEntity httpEntity = httpResponse.getEntity();
            result = EntityUtils.toString(httpEntity, "utf-8");
            EntityUtils.consume(httpEntity);
        } finally {
            try {
                if (httpResponse != null) {
                    httpResponse.close();
                }
            } catch(IOException e) {
                log.error("## release resouce error ##" + e);
            }
        }
        return result;
    }

    public static String getDigest(String string) {
        if (string == null) {
            return null;
        }
        char hexDigits[] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};
        byte[] btInput = string.getBytes(StandardCharsets.UTF_8);
        try {
            MessageDigest mdInst = MessageDigest.getInstance("SHA-256");
            mdInst.update(btInput);
            byte[] md = mdInst.digest();
            int j = md.length;
            char str[] = new char[j * 2];
            int k = 0;
            for (byte byte0 : md) {
                str[k++] = hexDigits[byte0 >>> 4 & 0xf];
                str[k++] = hexDigits[byte0 & 0xf];
            }
            return new String(str);
        } catch (NoSuchAlgorithmException e) {
            return null;
        }
    }



    //长语音识别
    public String asrLong(String url) throws Exception {
        String asrResult=null;
        int i=3;
        while (i-->0){
            URL urlfile=new URL(url);
            URLConnection uc=urlfile.openConnection();
            InputStream fis = null;
            try {
                fis = uc.getInputStream();
                i=0;
                long fileSize = uc.getContentLengthLong();
                // 预处理
                String fileName = urlfile.getFile().substring(urlfile.getFile().lastIndexOf('/') + 1);
                String taskId = prepare(fileName,fileSize, longLangType);


                byte[] slice = new byte[SLICE_SICE];
                int sliceId = 1;
                int offset = 0;

                while (offset<fileSize){
                    // 分片上传文件
                    int len = 0;
                    while (len < SLICE_SICE)
                    {
                        int result = fis.read(slice, len, slice.length - len);
                        if (result == -1) {
                            break;
                        }
                        offset += result;
                        len+=result;
                    }
                    // 上传分片
                    slice = Arrays.copyOfRange(slice, 0, len);
                    upload(taskId, sliceId++, slice);
                }


                // 合并文件
                merge(taskId);
                log.info("asrLong 合并文件成功" );
                //10分钟
                int y=200;
                // 轮询获取任务结果
                while (y-->0) {
                    try {
                        log.info("sleep a while ..." );
                        Thread.sleep(3000);
                    } catch (InterruptedException ignored) {}
                    String processResult = getProcess(taskId);
                    JSONObject taskProgress = JSONObject.parseObject(processResult);
                    if ("0".equals(taskProgress.getString("errorCode"))) {
                        JSONArray resultArray = taskProgress.getJSONArray("result");
                        Integer taskStatus = resultArray.getJSONObject(0).getInteger("status");
                        if (taskStatus == 9) {
                            log.info("asrLong 任务完成！");
                            break;
                        }

                        log.info("asrLong 任务处理中：" + taskStatus);
                    } else {
                        log.info("asrLong 获取任务进度失败！");
                    }
                }
                // 获取结果
                asrResult=getResult(taskId);
            } catch (Exception e) {
                Thread.sleep(3000);
                log.error("AudioUtil asrLong error, audioUrl:{}, error:{}", url, e);
            } finally {
                if (fis != null) {
                    fis.close();
                }
            }
        }
        return asrResult;
    }

    public String prepare(String fileName ,long fileSize , String langType) throws Exception {

        int sliceNum = (int) Math.ceil(fileSize * 1.0 / SLICE_SICE);
        String suffix = fileName.substring(fileName.indexOf(".") + 1, fileName.length());
        String curtime = String.valueOf(System.currentTimeMillis()/1000);
        String salt = UUID.randomUUID().toString();
        String signStr = longAppKey + salt + curtime +longAppSecret;
        String sign = encrypt(signStr, null);
        Map<String, String> map = new HashMap<>();
        String url = longHostUrl + "/api/audio/prepare";
        map.put("appKey", longAppKey);
        map.put("name", fileName);
        map.put("format", suffix);
        map.put("type", "1");
        map.put("langType", langType);
        map.put("curtime", curtime);
        map.put("salt", salt);
        map.put("sign", sign);
        map.put("signType", "v4");
        map.put("sliceNum", String.valueOf(sliceNum));
        map.put("fileSize", String.valueOf(fileSize));
        String result = requestForHttp(url, map);
        JSONObject resultObject = JSONObject.parseObject(result);
        if (!"0".equals(resultObject.getString("errorCode"))) {
            log.error("AudioUtil prepare 预处理失败！ error, fileName:{}, error:{}", fileName, result);
            throw new RuntimeException("预处理失败！" + result);
        }
        String taskId = resultObject.getString("result");
        log.info("预处理成功, taskid：" + taskId);
        return taskId;
    }

    public String prepare(File audioFile, String langType) throws Exception {

        long fileSize = audioFile.length();
        int sliceNum = (int) Math.ceil(fileSize * 1.0 / SLICE_SICE);
        String fileName = audioFile.getName();
        String suffix = fileName.substring(fileName.indexOf(".") + 1, fileName.length());
        String curtime = String.valueOf(System.currentTimeMillis()/1000);
        String salt = UUID.randomUUID().toString();
        String signStr = longAppKey + salt + curtime +longAppSecret;
        String sign = encrypt(signStr, null);
        Map<String, String> map = new HashMap<>();
        String url = longHostUrl + "/api/audio/prepare";
        map.put("appKey", longAppKey);
        map.put("name", fileName);
        map.put("format", suffix);
        map.put("type", "1");
        map.put("langType", langType);
        map.put("curtime", curtime);
        map.put("salt", salt);
        map.put("sign", sign);
        map.put("signType", "v4");
        map.put("sliceNum", String.valueOf(sliceNum));
        map.put("fileSize", String.valueOf(fileSize));
        String result = requestForHttp(url, map);
        JSONObject resultObject = JSONObject.parseObject(result);
        if (!"0".equals(resultObject.getString("errorCode"))) {
            log.error("AudioUtil prepare 预处理失败！ error, fileName:{}, error:{}", fileName, result);
            throw new RuntimeException("预处理失败！" + result);
        }
        String taskId = resultObject.getString("result");
        log.info("预处理成功, taskid：" + taskId);
        return taskId;
    }

    /**
     * 获取MessageDigest的加密结果
     * @param strSrc
     * @param encName
     * @return
     * @throws NoSuchAlgorithmException
     */
    public static String encrypt(String strSrc, String encName) throws NoSuchAlgorithmException {
        Preconditions.checkNotNull(strSrc);
        byte[] bt = strSrc.getBytes();
        if (encName == null || "".equals(encName)) {
            encName = "SHA-256";
        }
        MessageDigest md = MessageDigest.getInstance(encName);
        md.update(bt);
        return bytes2Hex(md.digest());
    }

    public static String bytes2Hex(byte[] bts) {
        String des = "";
        String tmp = null;
        for (int i = 0; i < bts.length; i++) {
            tmp = (Integer.toHexString(bts[i] & 0xFF));
            if (tmp.length() == 1) {
                des += "0";
            }
            des += tmp;
        }
        return des;
    }

    public String merge(String task) throws Exception {

        String curtime = String.valueOf(System.currentTimeMillis()/1000);
        String salt = UUID.randomUUID().toString();
        String signStr = longAppKey + salt + curtime +longAppSecret;
        String sign = encrypt(signStr, null);
        Map<String, String> map = new HashMap<>();
        String url = longHostUrl + "/api/audio/merge";
        map.put("appKey", longAppKey);
        map.put("q", task);
        map.put("curtime", curtime);
        map.put("salt", salt);
        map.put("sign", sign);
        map.put("signType", "v4");
        String result = requestForHttp(url, map);
        JSONObject resultObject = JSONObject.parseObject(result);
        if (!"0".equals(resultObject.getString("errorCode"))) {
            log.error("合并音频失败！" + result);
            throw new RuntimeException("合并音频失败！" + result);
        }
        return result;
    }

    public String getProcess(String task) throws Exception {
        String curtime = String.valueOf(System.currentTimeMillis()/1000);
        String salt = UUID.randomUUID().toString();
        String signStr = longAppKey + salt + curtime +longAppSecret;
        String sign = encrypt(signStr, null);
        Map<String, String> map = new HashMap<>();
        String url = longHostUrl + "/api/audio/get_progress";
        map.put("appKey", longAppKey);
        map.put("q", task);
        map.put("curtime", curtime);
        map.put("salt", salt);
        map.put("sign", sign);
        map.put("signType", "v4");
        String result = requestForHttp(url, map);
        JSONObject resultObject = JSONObject.parseObject(result);
        if (!"0".equals(resultObject.getString("errorCode"))) {
            log.error("获取任务处理状态失败！" + result);
            throw new RuntimeException("获取任务处理状态失败！" + result);
        }
        return result;
    }

    public String getResult(String task) throws Exception {
        String curtime = String.valueOf(System.currentTimeMillis()/1000);
        String salt = UUID.randomUUID().toString();
        String signStr = longAppKey + salt + curtime +longAppSecret;
        String sign = encrypt(signStr, null);
        Map<String, String> map = new HashMap<>();
        String url = longHostUrl + "/api/audio/get_result";
        map.put("appKey", longAppKey);
        map.put("q", task);
        map.put("curtime", curtime);
        map.put("salt", salt);
        map.put("sign", sign);
        map.put("signType", "v4");
        String result = requestForHttp(url, map);
        JSONObject resultObject = JSONObject.parseObject(result);
        if (!"0".equals(resultObject.getString("errorCode"))) {
            log.error("获取结果失败！" + result);
            throw new RuntimeException("获取结果失败！" + result);
        }
        return result;
    }

    public String upload(String task, Integer sliceId, byte[] slice) throws Exception {
        String curtime = String.valueOf(System.currentTimeMillis()/1000);
        String salt = UUID.randomUUID().toString();
        String signStr = longAppKey + salt + curtime +longAppSecret;
        String sign = encrypt(signStr, null);
        Map<String, String> map = new HashMap<>();
        String url = longHostUrl + "/api/audio/upload";
        map.put("appKey", longAppKey);
        map.put("type", "1");
        map.put("curtime", curtime);
        map.put("salt", salt);
        map.put("sign", sign);
        map.put("signType", "v4");
        map.put("q", task);
        map.put("sliceId", String.valueOf(sliceId));
        String result = requestForHttp(url, map, slice);
        JSONObject resultObject = JSONObject.parseObject(result);
        if (!"0".equals(resultObject.getString("errorCode"))) {
            log.error("上传音频失败！" + result);
            throw new RuntimeException("上传音频失败！" + result);
        }
        return result;
    }
    public static String requestForHttp(String url, Map<String,String> requestParams) throws Exception{
        String result = null;
        CloseableHttpClient httpClient = HttpClients.createDefault();
        /**HttpPost*/
        HttpPost httpPost = new HttpPost(url);
        List<NameValuePair> params = new ArrayList<NameValuePair>();
        Iterator<Map.Entry<String, String>> it = requestParams.entrySet().iterator();
        while (it.hasNext()) {
            Map.Entry<String, String> en = it.next();
            String key = en.getKey();
            String value = en.getValue();
            if (value != null) {
                params.add(new BasicNameValuePair(key, value));
            }
        }
        httpPost.setEntity(new UrlEncodedFormEntity(params,"UTF-8"));
        /**HttpResponse*/
        CloseableHttpResponse httpResponse = httpClient.execute(httpPost);
        try{
            HttpEntity httpEntity = httpResponse.getEntity();
            result = EntityUtils.toString(httpEntity, "utf-8");
            EntityUtils.consume(httpEntity);
        }finally{
            try{
                if (httpResponse!=null) {
                    httpResponse.close();
                }
            } catch (IOException e) {
                System.out.println("## release resouce error ##" + e);
            }
        }
        return result;
    }



    public static String requestForHttp(String url, Map<String,String> requestParams, byte[] slice) throws Exception{
        String result = null;
        CloseableHttpClient httpClient = HttpClients.createDefault();
        url = getUrlWithQueryString(url, requestParams);
        /**HttpPost*/
        HttpPost httpPost = new HttpPost(url);
        HttpEntity data = MultipartEntityBuilder.create()
                .setMode(HttpMultipartMode.BROWSER_COMPATIBLE)
                .addBinaryBody("file", slice, ContentType.IMAGE_JPEG, "audio")
                .build();
        httpPost.setEntity(data);

        /**HttpResponse*/
        CloseableHttpResponse httpResponse = httpClient.execute(httpPost);
        try {
            HttpEntity httpEntity = httpResponse.getEntity();
            result = EntityUtils.toString(httpEntity, "utf-8");
            EntityUtils.consume(httpEntity);
        } finally {
            try {
                if (httpResponse!=null) {
                    httpResponse.close();
                }
            } catch (IOException e) {
                System.out.println("## release resouce error ##" + e);
            }
        }
        return result;
    }

    /**
     * 根据api地址和参数生成请求URL
     *
     * @param url
     * @param params
     * @return
     */
    public static String getUrlWithQueryString(String url, Map<String, String> params) {
        if (params == null) {
            return url;
        }

        StringBuilder builder = new StringBuilder(url);
        if (url.contains("?")) {
            builder.append("&");
        } else {
            builder.append("?");
        }

        int i = 0;
        for (String key: params.keySet()) {
            String value = params.get(key);
            if (value == null) { // 过滤空的key
                continue;
            }

            if (i != 0) {
                builder.append('&');
            }

            builder.append(key);
            builder.append('=');
            builder.append(encode(value));

            i++;
        }

        return builder.toString();
    }

    /**
     * 进行URL编码
     *
     * @param input
     * @return
     */
    public static String encode(String input) {
        if (input == null) {
            return "";
        }

        try {
            return URLEncoder.encode(input, "utf-8");
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }

        return input;
    }
}
